
package krut.KRUT_Recording;

/*
 * @(#)Merge.java	1.2 01/03/13
 *
 * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

import java.io.File;
import javax.media.*;
import javax.media.format.*;
import javax.media.protocol.*;
import javax.media.protocol.DataSource;
import javax.media.datasink.*;
import java.util.Vector;


/**
 * Merge the tracks from different inputs and generate a QuickTime file
 * with the all the merged tracks. Documentation is available on the
 *  Java Media Framework website.
 */
public class Merge implements ControllerListener, DataSinkListener {
    
    Vector sourcesURLs = new Vector(1);
    Processor [] processors = null;
    String outputFile = null;
    String videoEncoding = "JPEG";
    String audioEncoding = "LINEAR";
    String outputType = FileTypeDescriptor.QUICKTIME;
    DataSource [] dataOutputs = null;
    DataSource merger = null;
    DataSource outputDataSource;
    Processor outputProcessor;
    ProcessorModel outputPM;
    DataSink outputDataSink;
    MediaLocator outputLocator;
    
    VideoFormat videoFormat = null;
    AudioFormat audioFormat = null;

    /** This parameter is used to stop the
     *  merging loop in the doMerge()
     *  method.
     */
    boolean done = false;

    /*  This checks if the recording should abort.
     *  It could be done with a local paramameter flag for
     *  aborted, that should be changed from the
     *  EncodingProgressBar instead.
     */
    public krut.KRUT_GUI.EncodingProgressBar myProgressBar;
    
    public Merge(String [] args) {
        parseArgs(args);
        if (sourcesURLs.size() < 2) {
            System.err.println("Need at least two source URLs");
            showUsage();
        } else {
            doMerge();
        }
    }
    
    public Merge(String [] args, krut.KRUT_GUI.EncodingProgressBar p) {
        myProgressBar = p;
        parseArgs(args);
        if (sourcesURLs.size() < 2) {
            System.err.println("Need at least two source URLs");
            showUsage();
        } else {
            doMerge();
        }
    }
    
    private void doMerge() {
        
        int i = 0;
        processors = new Processor[sourcesURLs.size()];
        dataOutputs = new DataSource[sourcesURLs.size()];
        
        for (i = 0; i < sourcesURLs.size(); i++) {
            String source = (String) sourcesURLs.elementAt(i);
            MediaLocator ml = new MediaLocator(source);
            ProcessorModel pm = new MyPM(ml);
            try {
                processors[i] = Manager.createRealizedProcessor(pm);
                dataOutputs[i] = processors[i].getDataOutput();
                processors[i].start();
            } catch (Exception e) {
                System.err.println("Failed to create a processor: " + e);
                System.exit(-1);
            }
        }
        
        // Merge the data sources from the individual processors
        try {
            merger = Manager.createMergingDataSource(dataOutputs);
            merger.connect();
            merger.start();
        } catch (Exception ex) {
            System.err.println("Failed to merge data sources: " + ex);
            System.exit(-1);
        }
        if (merger == null) {
            System.err.println("Failed to merge data sources");
            System.exit(-1);
        }
        /*
        try {
            Player p = Manager.createPlayer(merger);
            new com.sun.media.ui.PlayerWindow(p);
        } catch (Exception e) {
            System.err.println("Failed to create player " + e);
        }
         */
        
        // Create the output processor
        ProcessorModel outputPM = new MyPMOut(merger);
        
        try {
            outputProcessor = Manager.createRealizedProcessor(outputPM);
            outputDataSource = outputProcessor.getDataOutput();
        } catch (Exception exc) {
            System.err.println("Failed to create output processor: " + exc);
            System.exit(-1);
        }
        
        try {
            outputLocator = new MediaLocator(outputFile);
            outputDataSink = Manager.createDataSink(outputDataSource,
                    outputLocator);
            outputDataSink.open();
        } catch (Exception exce) {
            System.err.println("Failed to create output DataSink: " + exce);
            System.exit(-1);
        }
        
        outputProcessor.addControllerListener(this);
        outputDataSink.addDataSinkListener(this);
        System.err.println("Merging...");
        try {
            outputDataSink.start();
            outputProcessor.start();
        } catch (Exception excep) {
            System.err.println("Failed to start file writing: " + excep);
            System.exit(-1);
        }
        int count = 0;

        while (!done) {
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException ie) {
            }
            
            /** Just check if the merging has been cancelled.
             *  The first try is in case of a NullPointerException,
             *  in case the progress bar has not been
             *  initiated.
             */
            try {
                if (myProgressBar.cancelled) {
                    try {
                        done = true;
                        outputDataSink.stop();
                        outputProcessor.stop();
                        System.out.println("Stopped merge datasink");
                    } catch (Exception e) {
                        System.out.println("Couldn't stop merge datasink");
                        System.out.println(e);
                    }
                }
            } catch (Exception e) {}
            
            if (outputProcessor != null &&
                    (int)(outputProcessor.getMediaTime().getSeconds()) > count) {
                System.err.print(".");
                count = (int)(outputProcessor.getMediaTime().getSeconds());
            }
            
        }
        
        if (outputDataSink != null) {
            outputDataSink.close();
        }
        synchronized (this) {
            if (outputProcessor != null) {
                outputProcessor.close();
            }
        }
        System.err.println("Done!");
    }
    
    public void controllerUpdate(ControllerEvent ce) {
        if (ce instanceof EndOfMediaEvent) {
            synchronized (this) {
                outputProcessor.close();
                outputProcessor = null;
            }
        }
    }
    
    public void dataSinkUpdate(DataSinkEvent dse) {
        if (dse instanceof EndOfStreamEvent) {
            done = true;
        } else if (dse instanceof DataSinkErrorEvent) {
            done = true;
        }
    }
    
    class MyPM extends ProcessorModel {
        
        MediaLocator inputLocator;
        
        public MyPM(MediaLocator inputLocator) {
            this.inputLocator = inputLocator;
        }
        
        public ContentDescriptor getContentDescriptor() {
            return new ContentDescriptor(ContentDescriptor.RAW);
        }
        
        public DataSource getInputDataSource() {
            return null;
        }
        
        public MediaLocator getInputLocator() {
            return inputLocator;
        }
        
        public Format getOutputTrackFormat(int index) {
            return null;
        }
        
        public int getTrackCount(int n) {
            return n;
        }
        
        public boolean isFormatAcceptable(int index, Format format) {
            if (videoFormat == null) {
                videoFormat = new VideoFormat(videoEncoding);
            }
            if (audioFormat == null) {
                audioFormat = new AudioFormat(audioEncoding);
            }
            if (format.matches(videoFormat) || format.matches(audioFormat))
                return true;
            else
                return false;
        }
    }
    
    class MyPMOut extends ProcessorModel {
        
        DataSource inputDataSource;
        
        public MyPMOut(DataSource inputDataSource) {
            this.inputDataSource = inputDataSource;
        }
        
        public ContentDescriptor getContentDescriptor() {
            return new FileTypeDescriptor(outputType);
        }
        
        public DataSource getInputDataSource() {
            return inputDataSource;
        }
        
        public MediaLocator getInputLocator() {
            return null;
        }
        
        public Format getOutputTrackFormat(int index) {
            return null;
        }
        
        public int getTrackCount(int n) {
            return n;
        }
        
        public boolean isFormatAcceptable(int index, Format format) {
            if (videoFormat == null) {
                videoFormat = new VideoFormat(videoEncoding);
            }
            if (audioFormat == null) {
                audioFormat = new AudioFormat(audioEncoding);
            }
            if (format.matches(videoFormat) || format.matches(audioFormat))
                return true;
            else
                return false;
        }
    }
    
    private void showUsage() {
        System.err.println("Usage: Merge <url1> <url2> [<url3> ... ] [-o <out URL>] [-v <video_encoding>] [-a <audio_encoding>] [-t <content_type>]");
    }
    
    private void parseArgs(String [] args) {
        int i = 0;
        while (i < args.length) {
            if (args[i].equals("-h")) {
                showUsage();
            } else if (args[i].equals("-o")) {
                i++;
                outputFile = args[i];
            } else if (args[i].equals("-t")) {
                i++;
                outputType = args[i];
            } else if (args[i].equals("-v")) {
                i++;
                videoEncoding = args[i];
            } else if (args[i].equals("-a")) {
                i++;
                audioEncoding = args[i];
            } else {
                sourcesURLs.addElement(args[i]);
            }
            i++;
        }
        
        if (outputFile == null) {
            outputFile = "file:" + System.getProperty("user.dir") +
                    File.separator + "merged.mov";
        }
    }
    
    public static void main(String [] args) {
        new Merge(args);
        System.exit(0);
    }
}